<?php

namespace Hilaoyu\Utils;

use Hilaoyu\Utils\Traits\ErrorsTrait;

/**
 * Created by laoyu.
 * User: hilaoyu@qq.com
 * Date: 2018/3/8
 * Time: 9:45
 *
 * 远程http交互类
 *
 *
 */
class UtilHttp
{
    use ErrorsTrait;

    protected $basic_uri = '';
    protected $method = 'post';
    protected $global_headers = [];
    protected $check_result_http_code_status = true;

    protected $ssl_pem_cert = null;
    protected $ssl_pem_key = null;
    protected $ssl_pem_verify = false;

    protected $response_http_code = 0;
    protected $response_content = '';

    protected $connect_timeout = 60;
    protected $timeout = 0;

    protected $proxy_type = '';
    protected $proxy_socks5_host_port = '';
    protected $proxy_socks5_user_pass = '';

    public function __construct($basic_uri = '', $method = 'post')
    {
        $this->setBasicUri($basic_uri)->setMethod($method);
    }

    public function setBasicUri($basic_uri = '')
    {
        $this->basic_uri = trim($basic_uri);
        return $this;
    }

    public function setMethod($method = 'post')
    {
        $method = strtolower(trim($method));
        if ($method) {
            $this->method = $method;
        }

        return $this;
    }

    public function setConnectTimeout($time = 60)
    {
        $time = intval($time);
        if ($time >= 0) {
            $this->connect_timeout = $time;
        }

        return $this;
    }

    public function setTimeout($time = 0)
    {
        $time = intval($time);
        if ($time >= 0) {
            $this->timeout = $time;
        }

        return $this;
    }

    public function setGlobalHeaders($headers = [])
    {
        $this->global_headers = (array)$headers;
        return $this;
    }


    public function useSsl($cert, $key, $verify = false)
    {
        $cert = trim($cert);
        $key = trim($key);
        if (!file_exists($cert)) {
            $this->setError('cert不存在');
            return false;
        }

        if (!file_exists($key)) {
            $this->setError('key不存在');
            return false;
        }

        $this->ssl_pem_cert = $cert;
        $this->ssl_pem_key = $key;
        $this->ssl_pem_verify = (bool)$verify;

        return true;
    }

    public function useProxySocks5($host, $port = 1080, $user = '', $password = '')
    {
        $this->proxy_type = 'socks5';
        $this->proxy_socks5_host_port = trim($host) . ':' . trim($port);
        if ($user = trim($user)) {
            $this->proxy_socks5_user_pass = $user . ':' . trim($password);
        }
    }
    public function useProxyNone()
    {
        $this->proxy_type = '';
        $this->proxy_socks5_host_port = '';
        $this->proxy_socks5_user_pass = '';
    }

    public function checkResultHttpCode($check = true)
    {
        $this->check_result_http_code_status = (bool)$check;
    }

    public function clearResponse()
    {
        $this->response_http_code = 0;
        $this->response_content = '';
    }

    public function getResponseHttpCode()
    {
        return $this->response_http_code;
    }

    public function getResponseContent()
    {
        return $this->response_content;
    }


    /*
     * 远程交互，返回json
     * */
    public function httpRequestJson($uri, $params = [], $method = "post", $headers = [])
    {
        $headers['X-Requested-With'] = 'XMLHttpRequest';
        $headers['Accept'] = 'application/json';
        $re = $this->httpRequest($uri, $params, $method, $headers);
        if (false !== $re) {
            $re = json_decode(trim($re, chr(239) . chr(187) . chr(191)), true);
            if (false === $re) {
                $this->setError('解析JSON出错');
            }
        }
        return $re;
    }

    /*
     * 远程下载文件
     * @save_path 可选
     *
     * 返回 文件数据（不传save_path） 或文件保存地址(传了 save_path),
     * */
    public function httpRequestFile($uri, $save_path = '')
    {
        //创建保存目录
        $save_path = trim($save_path);
        if (!empty($save_path)) {
            if (substr($save_path, -1) == '/') {
                $save_path .= UtilFile::basename(parse_url($uri, PHP_URL_PATH));
            }
            $dir = UtilFile::dirname($save_path);
            if (!UtilFile::checkDir($dir)) {
                $this->setError('存储目录不可写');
                return false;
            }
        }


        $stream = $this->httpRequest($uri, [], 'get');
        if (false !== $stream) {

            if ($save_path) {
                $fp = @fopen($save_path, 'w');
                if (!$fp) {
                    $this->setError('写入文件错误');
                    return false;
                }
                $write = fwrite($fp, $stream);
                fclose($fp);
                if (FALSE === $write) {
                    $this->setError('写入文件错误!');
                    return false;
                }
                unset($stream, $uri);
                if (!is_file($save_path)) {
                    $this->setError('写入文件失败!');
                    return false;
                }
                return $save_path;
            }

            return $stream;
        }
        return false;

    }

    public function buildCurl($uri, $params = [], $method = "", $headers = [])
    {
        $uri = trim($uri);
        if ($method) {
            $this->setMethod($method);
        }

        if ($this->basic_uri) {
            $uri = rtrim($this->basic_uri,"/") .'/'. ltrim($uri,"/");
        }

        if (in_array($this->method, ['get', 'delete']) && !empty($params)) {
            $uri = static::buildUrl($uri, $params);
        }

        $ch = curl_init();

        if('socks5' == $this->proxy_type){
            curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
            curl_setopt($ch, CURLOPT_PROXY, $this->proxy_socks5_host_port);
            //curl_setopt($ch, CURLOPT_PROXYPORT, 8880);
            if($this->proxy_socks5_user_pass){
                curl_setopt($ch,CURLOPT_PROXYUSERPWD, $this->proxy_socks5_user_pass);
            }

        }

        curl_setopt($ch, CURLOPT_URL, $uri);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

        if ($this->ssl_pem_cert && $this->ssl_pem_key) {
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, $this->ssl_pem_cert);
            curl_setopt($ch, CURLOPT_SSLCERTPASSWD, '');
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, $this->ssl_pem_key);


        }
        if (!$this->ssl_pem_verify) {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }
        if (!in_array($this->method, ['get', 'delete'])) {
            // post数据
            curl_setopt($ch, CURLOPT_POST, 1);
            // post的变量
            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);

            if (!isset($headers['Content-Type'])) {
                $headers['Content-Type'] = 'multipart/form-data';
            }
        }

        //处理 method
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method));

        $headers = array_merge($this->global_headers, $headers);

        $headers['X-HTTP-Method-Override'] = strtoupper($this->method);


        if (!empty($headers)) {
            $curl_headers = [];
            foreach ($headers as $header_key => $header_value) {
                if (is_string($header_key) && false === stripos($header_value, ':')) {
                    $header_value = $header_key . ':' . $header_value;
                }
                $curl_headers[] = $header_value;
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $curl_headers);
        }

        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
        if ($this->timeout > 0) {
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        }
        return $ch;
    }

    /*
     * 获取远程内容
     * */
    public function httpRequest($uri, $params = [], $method = "", $headers = [])
    {
        $ch = $this->buildCurl($uri, $params, $method, $headers);
        $http_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);

        curl_setopt($ch, CURLOPT_HEADER, false);

        $this->response_content = curl_exec($ch);

        $this->response_http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);


        if ($curlError) {
            $this->setError($curlError, 'curl_error');
        }

        if ($this->check_result_http_code_status && !UtilStr::startsWith($this->response_http_code, '20')) {

            $this->setError($http_url, 'http_url');
            $this->setError($this->response_http_code, 'http_code');
            $this->setError($this->response_content, 'content');
            return false;
        }

        return $this->response_content;

    }

    public function httpRequestHeaderOnly($uri, $params = [], $method = "", $headers = [])
    {

        $response_headers = [];
        $ch = $this->buildCurl($uri, $params, $method, $headers);

        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_NOBODY, true);

        $tmp = curl_exec($ch);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($curlError) {
            $this->setError($curlError, 'curl_error');
            return false;
        }

        (preg_match('/Content-Length:\s([0-9].+?)\s/', $tmp, $arr)) && $response_headers['length'] = trim($arr[1]);
        (preg_match('/Content-Type:\s(.*)\s/', $tmp, $arr)) && $response_headers['mimetype'] = trim($arr[1]);
        (preg_match('/filename=(.*)/i', $tmp, $arr)) && $response_headers['filename'] = trim($arr[1]);
        (preg_match('/(HTTP.*)(\s\d{3}\s)/', $tmp, $arr)) && $response_headers['status'] = trim($arr[2]);

        return $response_headers;
    }

    public static function buildUrl($url, $params)
    {
        if(!is_array($params)){
            return $url;
        }
        $parse = parse_url($url);
        if (array_key_exists('query', $parse)) {
            parse_str($parse['query'], $query_arr);
        } else {
            $query_arr = [];
        }


        $query_arr = array_merge($query_arr, $params);
        $query = http_build_query($query_arr);
        $parse['query'] = $query;

        return  static::httpBuildUrl($parse);
    }

    public static function Curl($uri, $params = [], $method = "", $headers = [])
    {
        return (new static())->buildCurl($uri,$params,$method,$headers);

    }

    public static function Request($uri, $params = [], $method = "post", $headers = [])
    {
        $instance = UtilSupport::makeClassSingle(static::class);
        return $instance->httpRequest($uri, $params, $method, $headers);
    }

    public static function RequestHeaderOnly($uri, $params = [], $method = "post", $headers = [])
    {
        $instance = UtilSupport::makeClassSingle(static::class);
        return $instance->httpRequestHeaderOnly($uri, $params, $method, $headers);
    }

    public static function RequestJson($uri, $params = [], $method = "post", $headers = [])
    {
        $instance = UtilSupport::makeClassSingle(static::class);
        return $instance->httpRequestJson($uri, $params, $method, $headers);
    }

    public static function RequestFile($uri, $save_path = '')
    {
        $instance = UtilSupport::makeClassSingle(static::class);
        return $instance->httpRequestFile($uri, $save_path);
    }

    /**
     * Build a URL.
     *
     * The parts of the second URL will be merged into the first according to
     * the flags argument.
     *
     * @param mixed $url (part(s) of) an URL in form of a string or
     *                       associative array like parse_url() returns
     * @param mixed $parts same as the first argument
     * @param int $flags a bitmask of binary or'ed HTTP_URL constants;
     *                       HTTP_URL_REPLACE is the default
     * @param array $new_url if set, it will be filled with the parts of the
     *                       composed url like parse_url() would return
     * @return string
     */
    public static function httpBuildUrl($url, $parts = array(), $flags = 1, &$new_url = array())
    {
        if(function_exists('http_build_url')){
            return http_build_url($url,$parts,$flags,$new_url);
        }
        if (!defined('HTTP_URL_REPLACE')) {
            define('HTTP_URL_REPLACE', 1);
        }
        if (!defined('HTTP_URL_JOIN_PATH')) {
            define('HTTP_URL_JOIN_PATH', 2);
        }
        if (!defined('HTTP_URL_JOIN_QUERY')) {
            define('HTTP_URL_JOIN_QUERY', 4);
        }
        if (!defined('HTTP_URL_STRIP_USER')) {
            define('HTTP_URL_STRIP_USER', 8);
        }
        if (!defined('HTTP_URL_STRIP_PASS')) {
            define('HTTP_URL_STRIP_PASS', 16);
        }
        if (!defined('HTTP_URL_STRIP_AUTH')) {
            define('HTTP_URL_STRIP_AUTH', 32);
        }
        if (!defined('HTTP_URL_STRIP_PORT')) {
            define('HTTP_URL_STRIP_PORT', 64);
        }
        if (!defined('HTTP_URL_STRIP_PATH')) {
            define('HTTP_URL_STRIP_PATH', 128);
        }
        if (!defined('HTTP_URL_STRIP_QUERY')) {
            define('HTTP_URL_STRIP_QUERY', 256);
        }
        if (!defined('HTTP_URL_STRIP_FRAGMENT')) {
            define('HTTP_URL_STRIP_FRAGMENT', 512);
        }
        if (!defined('HTTP_URL_STRIP_ALL')) {
            define('HTTP_URL_STRIP_ALL', 1024);
        }


        is_array($url) || $url = parse_url($url);
        is_array($parts) || $parts = parse_url($parts);

        isset($url['query']) && is_string($url['query']) || $url['query'] = null;
        isset($parts['query']) && is_string($parts['query']) || $parts['query'] = null;

        $keys = array('user', 'pass', 'port', 'path', 'query', 'fragment');

        // HTTP_URL_STRIP_ALL and HTTP_URL_STRIP_AUTH cover several other flags.
        if ($flags & HTTP_URL_STRIP_ALL) {
            $flags |= HTTP_URL_STRIP_USER | HTTP_URL_STRIP_PASS
                | HTTP_URL_STRIP_PORT | HTTP_URL_STRIP_PATH
                | HTTP_URL_STRIP_QUERY | HTTP_URL_STRIP_FRAGMENT;
        } elseif ($flags & HTTP_URL_STRIP_AUTH) {
            $flags |= HTTP_URL_STRIP_USER | HTTP_URL_STRIP_PASS;
        }

        // Schema and host are alwasy replaced
        foreach (array('scheme', 'host') as $part) {
            if (isset($parts[$part])) {
                $url[$part] = $parts[$part];
            }
        }

        if ($flags & HTTP_URL_REPLACE) {
            foreach ($keys as $key) {
                if (isset($parts[$key])) {
                    $url[$key] = $parts[$key];
                }
            }
        } else {
            if (isset($parts['path']) && ($flags & HTTP_URL_JOIN_PATH)) {
                if (isset($url['path']) && substr($parts['path'], 0, 1) !== '/') {
                    // Workaround for trailing slashes
                    $url['path'] .= 'a';
                    $url['path'] = rtrim(
                            str_replace(UtilFile::basename($url['path']), '', $url['path']),
                            '/'
                        ) . '/' . ltrim($parts['path'], '/');
                } else {
                    $url['path'] = $parts['path'];
                }
            }

            if (isset($parts['query']) && ($flags & HTTP_URL_JOIN_QUERY)) {
                if (isset($url['query'])) {
                    parse_str($url['query'], $url_query);
                    parse_str($parts['query'], $parts_query);

                    $url['query'] = http_build_query(
                        array_replace_recursive(
                            $url_query,
                            $parts_query
                        )
                    );
                } else {
                    $url['query'] = $parts['query'];
                }
            }
        }

        if (isset($url['path']) && $url['path'] !== '' && substr($url['path'], 0, 1) !== '/') {
            $url['path'] = '/' . $url['path'];
        }

        foreach ($keys as $key) {
            $strip = 'HTTP_URL_STRIP_' . strtoupper($key);
            if ($flags & constant($strip)) {
                unset($url[$key]);
            }
        }

        $parsed_string = '';

        if (!empty($url['scheme'])) {
            $parsed_string .= $url['scheme'] . '://';
        }

        if (!empty($url['user'])) {
            $parsed_string .= $url['user'];

            if (isset($url['pass'])) {
                $parsed_string .= ':' . $url['pass'];
            }

            $parsed_string .= '@';
        }

        if (!empty($url['host'])) {
            $parsed_string .= $url['host'];
        }

        if (!empty($url['port'])) {
            $parsed_string .= ':' . $url['port'];
        }

        if (!empty($url['path'])) {
            $parsed_string .= $url['path'];
        }

        if (!empty($url['query'])) {
            $parsed_string .= '?' . $url['query'];
        }

        if (!empty($url['fragment'])) {
            $parsed_string .= '#' . $url['fragment'];
        }

        $new_url = $url;

        return $parsed_string;
    }

    public static function paramsParse($params = [], $urlencode = false, $arr = [], $keypre = '')
    {
        if (!is_array($params)) {
            return $arr;
        }
        foreach ($params as $key => $param) {
            $param_key = $keypre !== '' ? ($keypre . '[' . $key . ']') : $key;
            if (is_array($param)) {
                $arr = static::paramsParse($param, $urlencode, $arr, $param_key);
            } else {
                $arr[$param_key] = $urlencode ? urlencode($param) : $param;
            }
        }

        return $arr;
    }

    public static function paramsArrayToStr($params = [], $urlencode = false)
    {
        $params_str = '';
        if (is_string($params)) {
            $params = trim($params);
            $params_str = $urlencode ? urlencode($params) : $params;
        } else {
            $params = (array)$params;
            $params = static::paramsParse($params, $urlencode);
            $params_tmp = [];
            foreach ($params as $k => $v) {
                $params_tmp[] = $k . '=' . $v;
            }
            $params_str = implode('&', $params_tmp);
        }
        return $params_str;
    }


    public static function buildFormDataItem(array $data, $key, $value, $mimeBoundary)
    {
        array_push($data, '--' . $mimeBoundary);
        array_push($data, "Content-Disposition: form-data; name=\"$key\"");
        array_push($data, '');
        array_push($data, $value);
        return $data;
    }

    public static function escapeQuotes($str)
    {
        $find = array("\\", "\"");
        $replace = array("\\\\", "\\\"");
        return str_replace($find, $replace, $str);
    }

    public static function buildUploadFormDataAndHeaders($fieldName, $fileBody, $fileName, array $fields = [], array &$headers = array(), $mimeType = 'application/octet-stream')
    {
        $data = array();
        $mimeBoundary = md5(microtime());

        foreach ($fields as $key => $val) {
            $data = static::buildFormDataItem($data, $key, $val, $mimeBoundary);
        }

        // set image body
        array_push($data, '--' . $mimeBoundary);
        $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType;
        $finalFileName = static::escapeQuotes($fileName);
        array_push($data, "Content-Disposition: form-data; name=\"$fieldName\"; filename=\"$finalFileName\"");
        array_push($data, "Content-Type: $finalMimeType");
        array_push($data, '');
        array_push($data, $fileBody);
        array_push($data, '--' . $mimeBoundary . '--');
        array_push($data, '');
        $body = implode("\r\n", $data);
        $contentType = 'multipart/form-data; boundary=' . $mimeBoundary;
        $headers['Content-Type'] = $contentType;
        return $body;
    }

    public static function proxySocks5($host, $port = 1080, $user = '', $password = ''){
        $_this = new static();
        $_this->useProxySocks5($host,$port,$user,$password);
        return $_this;
    }
}
